﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Content;
using MarioArena.Multiplayer.TCP;
using MarioArena.Animation;
using Microsoft.Xna.Framework;
using MarioArena.World.Projectiles;
using Microsoft.Xna.Framework.Audio;
using MarioArena.Multiplayer;
using System.Windows.Forms;
using MarioArena.HUD;
using MarioArena.Characters;

namespace MarioArena.World
{
    public class GameManager
    {
        public static GraphicsDevice GraphicsDevice;
        public static ContentManager Content;

        public int HalfScreenWidth;
        public int HalfScreenHeight;

        public float GameVolume { get; set; }
        public float MusicVolume { get; set; }
        public GameState GameState { get; set; } // Default = lobby

        // Store gamecontent here (can be reached from anywhere)
        public Texture2D CrosshairTexture { get; set; }
        public Texture2D FireballSpritesheet { get; set; }
        public Texture2D SpecialFireballSpritesheet { get; set; }
        public Texture2D ExplosionSmallSpritesheet { get; set; }
        public Texture2D ExplosionDeathSpritesheet { get; set; }
        public SoundEffect FireballLaunchSound { get; set; }
        public SoundEffect FireballExplosionSound { get; set; }
        public SoundEffect DeathExplosionSound { get; set; }
        public SoundEffect DeathScreamSound { get; set; }
        public SpriteFont NametagFont { get; set; }

        public List<Opponent> Opponents { get; set; }
        public List<AbstractProjectile> Projectiles { get; set; }
        public List<Explosion> Explosions { get; set; }
        public string ReceivedLevelString { get; set; }

        public bool ArePlayersDoneLoading { get; set; }
        public bool GameHasEnded { get; set; }

        // Mario spritesheets
        private Texture2D mMarioPassiveSpritesheet;
        private Texture2D mMarioRunSpritesheet;
        private Texture2D mMarioJumpSpritesheet;
        private Texture2D mMarioFallSpritesheet;


        #region Singleton
        private static GameManager instance = new GameManager();
        public static GameManager Instance
        {
            get
            {
                return instance;
            }
        }
        private GameManager()
        {
            this.GameState = GameState.Lobby;
            this.Opponents = new List<Opponent>();
            this.Projectiles = new List<AbstractProjectile>();
            this.Explosions = new List<Explosion>();
        }
        #endregion

        /// <summary>
        /// Loads all game content.
        /// </summary>
        public void LoadContent()
        {
            this.HalfScreenWidth = GameManager.GraphicsDevice.Viewport.Width / 2;
            this.HalfScreenHeight = GameManager.GraphicsDevice.Viewport.Height / 2;

            this.GameVolume = 1.0f;
            this.MusicVolume = 1.0f;

            // Load all game content only once
            this.CrosshairTexture = Content.Load<Texture2D>("Sprites/Misc/crosshair");
            this.FireballSpritesheet = GameManager.Content.Load<Texture2D>("Sprites/Animations/fireball2");
            this.SpecialFireballSpritesheet = GameManager.Content.Load<Texture2D>("Sprites/Animations/specialfireball");
            this.FireballLaunchSound = Content.Load<SoundEffect>("Sounds/Projectiles/fireball_launch");
            this.FireballExplosionSound = Content.Load<SoundEffect>("Sounds/Projectiles/fireball_explosion");
            this.DeathExplosionSound = Content.Load<SoundEffect>("Sounds/death_explosion");
            this.DeathScreamSound = Content.Load<SoundEffect>("Sounds/death_scream1");
            this.NametagFont = Content.Load<SpriteFont>("Fonts/Nametag");

            // Mario spritesheets
            this.mMarioPassiveSpritesheet = Content.Load<Texture2D>("Sprites/Animations/passive");
            this.mMarioRunSpritesheet = Content.Load<Texture2D>("Sprites/Animations/run");
            this.mMarioJumpSpritesheet = Content.Load<Texture2D>("Sprites/Animations/jump");
            this.mMarioFallSpritesheet = Content.Load<Texture2D>("Sprites/Animations/fall");

            // Explosion spritesheets
            this.ExplosionSmallSpritesheet = Content.Load<Texture2D>("Sprites/Animations/explosion_small");
            this.ExplosionDeathSpritesheet = Content.Load<Texture2D>("Sprites/Animations/explosion_death");
        }

        /// <summary>
        /// Starts the game.
        /// </summary>
        public void StartGame()
        {
            if (!this.ReceivedLevelString.Equals(""))
            {
                LoadLevel(this.ReceivedLevelString);
                Player.Instance.Character.Position = TileEngine.Instance.Playerstart;

                TCPConnection.Instance.Send("6_"); // Let the server know we are done loading and ready to start the game
            }
            else TCPConnection.Instance.Disconnect();
                    // TODO: Close game
        }

        /// <summary>
        /// Loads the level.
        /// </summary>
        /// <param name="levelString">The level string to load.</param>
        private void LoadLevel(string levelString)
        {
            TileEngine.Instance.LoadTilesNew(levelString);
        }

        /// <summary>
        /// Creates an AnimationCollection instance which should be used for each Mario instance.
        /// </summary>
        /// <returns>Mario AnimationCollection.</returns>
        public AnimationSet CreateMarioAnimations()
        {
            SpriteAnimation passiveAnimation = new SpriteAnimation(this.mMarioPassiveSpritesheet, 1);
            SpriteAnimation runAnimation = new SpriteAnimation(this.mMarioRunSpritesheet, 3);
            SpriteAnimation jumpAnimation = new SpriteAnimation(this.mMarioJumpSpritesheet, 2);
            SpriteAnimation fallAnimation = new SpriteAnimation(this.mMarioFallSpritesheet, 1);
            passiveAnimation.scale = 1.5f;
            runAnimation.scale = 1.5f;
            runAnimation.framesPerSecond = 9;
            runAnimation.isLooping = true;
            jumpAnimation.scale = 1.5f;
            jumpAnimation.framesPerSecond = 9;
            fallAnimation.scale = 1.5f;

            return new AnimationSet(passiveAnimation, runAnimation, jumpAnimation, fallAnimation);
        }

        /// <summary>
        /// Spawns a new projectile.
        /// </summary>
        /// <param name="type">The type of the projectile ('10', '11', '20', '21', etc).</param>
        /// <param name="origin">The origin of the projectile.</param>
        /// <param name="direction">The direction of the projectile (will be normalized).</param>
        public void SpawnProjectile(int id, string type, Vector2 origin, Vector2 direction)
        {
            AbstractProjectile projectile;
            if (type.Equals("11"))
                projectile = new SpecialFireball(id, origin, direction);
            else // default type = 10
                projectile = new Fireball(id, origin, direction);

            lock (this.Projectiles)
            {
                this.Projectiles.Add(projectile);
            }

            // Sound is played in TcpConnection
        }

        /// <summary>
        /// Explodes a projectile at the given position.
        /// </summary>
        /// <param name="id">The id of the projectile to explode.</param>
        /// <param name="xPosition">X position where the projectile should explode</param>
        /// <param name="yPosition">Y position where the projectile should explode</param>
        public void ExplodeProjectile(int id, int xPosition, int yPosition)
        {
            Vector2 receivedPosition = new Vector2(xPosition, yPosition);

            lock (this.Projectiles)
            {
                int projectileCount = this.Projectiles.Count;
                for (int i = projectileCount - 1; i >= 0; i--)
                {
                    if (this.Projectiles[i].Id.Equals(id))
                    {
                        
                        Explosion explosion = new Explosion(receivedPosition, Explosion.Type.Small); //use position received from server
                        //Explosion explosion = new Explosion(this.Projectiles[i].Position, Explosion.Type.Small); //without receiving position
                        this.Explosions.Add(explosion);
                        this.Projectiles.Remove(this.Projectiles[i]);
                        this.FireballExplosionSound.Play(0.2f * this.GameVolume, 0, 0);
                    }
                }
            }
        }

        /// <summary>
        /// Deletes a projectile from the general list of projectiles.
        /// </summary>
        /// <param name="projectile">The projectile object to delete.</param>
        public void DeleteProjectile(AbstractProjectile projectile)
        {
            lock (this.Projectiles)
            {
                this.Projectiles.Remove(projectile);
                Console.WriteLine("Projectile removed!");
            }
        }

        /// <summary>
        /// Damages the player.
        /// </summary>
        /// <param name="playerId">The id of the player who gets damaged.</param>
        /// <param name="projectileId">The id of the projectile to be destroyed. (pass -1 for no destroy)</param>
        public void DamagePlayer(int playerId, int projectileId, int damage)
        {
            if (Player.Instance.Id.Equals(playerId))
            {
                Console.WriteLine("Player himself got hit.");
                Player.Instance.Character.Damage(damage);
            }
            else
            {
                lock (this.Opponents)
                {
                    int opponentCount = this.Opponents.Count;
                    for (int i = opponentCount - 1; i >= 0; i--)
                    {
                        if (this.Opponents[i].Id.Equals(playerId))
                        {
                            Console.WriteLine(this.Opponents[i].Name + " got hit.");
                            this.Opponents[i].Character.Damage(damage);
                        }
                    }
                }
            }
        }

        /// <summary>
        /// Makes the player die by the hand of another player.
        /// </summary>
        /// <param name="playerId">Id of the player that is dead.</param>
        public void KillPlayer(int playerId, int killerId)
        {
            string victimString = "You were";
            if (Player.Instance.Id.Equals(playerId))
            {
                Player.Instance.Character.Die();
                Overlay.ShowRespawnDisplay();
            }
            else
            {
                lock (this.Opponents)
                {
                    int opponentCount = this.Opponents.Count;
                    for (int i = opponentCount - 1; i >= 0; i--)
                    {
                        if (this.Opponents[i].Id.Equals(playerId))
                        {
                            victimString = this.Opponents[i].Name + " was";
                            this.Opponents[i].Character.Die();
                        }
                    }
                }
            }
            GameManager.Instance.AwardPoint(killerId);
            //Show kill notification on screen.
            string killerName = GetPlayernameById(killerId);
            string killNotificationString = victimString + " killed by " + killerName;
            Overlay.ShowNotification(killNotificationString);
            Overlay.ShowSplashMessage(killNotificationString);
        }

        /// <summary>
        /// Awards player a point
        /// </summary>
        /// <param name="playerId">Id of the player that is dead.</param>
        public void AwardPoint(int playerId)
        {
            if (Player.Instance.Id.Equals(playerId))
            {
                Console.WriteLine("You were awarded a point.");
                Player.Instance.Character.AwardPoint();
            }
            else
            {
                lock (this.Opponents)
                {
                    int opponentCount = this.Opponents.Count;
                    for (int i = opponentCount - 1; i >= 0; i--)
                    {
                        if (this.Opponents[i].Id.Equals(playerId))
                        {
                            Console.WriteLine(this.Opponents[i].Name + " was awarded a point.");
                            this.Opponents[i].Character.AwardPoint();
                        }
                    }
                }
            }
        }

        /// <summary>
        /// Makes the player die for a certain reason.
        /// </summary>
        /// <param name="playerId">Id of the player that is dead.</param>
        public void DiePlayer(int playerId, string reasonMessage)
        {
            string victimString = "You";
            if (Player.Instance.Id.Equals(playerId))
            {
                Player.Instance.Character.Die();
                Overlay.ShowRespawnDisplay();
            }
            else
            {
                lock (this.Opponents)
                {
                    int opponentCount = this.Opponents.Count;
                    for (int i = opponentCount - 1; i >= 0; i--)
                    {
                        if (this.Opponents[i].Id.Equals(playerId))
                        {
                            victimString = this.Opponents[i].Name;
                            this.Opponents[i].Character.Die();
                        }
                    }
                }
            }
            //Show kill notification on screen.
            string killNotificationString = victimString + " " + reasonMessage;
            Overlay.ShowNotification(killNotificationString);
        }

        /// <summary>
        /// Respawns a player at a certain location.
        /// </summary>
        /// <param name="playerId">Id of the player that respawns.</param>
        /// <param name="tileIndexX">X index in the level grid where the player will spawn.</param>
        /// <param name="tileIndexY">Y index in the level grid where the player will spawn.</param>
        public void Respawn(int playerId, int tileIndexX, int tileIndexY)
        {
            if (Player.Instance.Id.Equals(playerId))
            {
                Console.WriteLine("You respawned.");
                Player.Instance.Character.Respawn(tileIndexX, tileIndexY);
            }
            else
            {
                lock (this.Opponents)
                {
                    int opponentCount = this.Opponents.Count;
                    for (int i = opponentCount - 1; i >= 0; i--)
                    {
                        if (this.Opponents[i].Id.Equals(playerId))
                        {
                            Console.WriteLine(this.Opponents[i].Name + " respawned.");
                            this.Opponents[i].Character.Respawn(tileIndexX, tileIndexY);
                        }
                    }
                }
            }
        }

        /// <summary>
        /// Shows the '10 seconds remaining' signal on-screen.
        /// </summary>
        public void TenSecondsRemaining()
        {
            Console.WriteLine("10 seconds remaining!");
            Overlay.ShowNotification("10 seconds until game ends!");
        }

        /// <summary>
        /// The game has ended.
        /// </summary>
        /// <param name="winnerIds">Id(s) of the player(s) that win(s)</param>
        public void GameEnded(int[] winnerIds)
        {
            string winnerStringEnd = "";
            string winnerStringStart = "The winner is ";
            if (winnerIds.Length > 1)
                winnerStringStart = "The winners are: ";

            for (int i = 0; i < winnerIds.Length; i++)
            {
                if (i > 0)
                    winnerStringEnd += " and ";
                winnerStringEnd += GetPlayernameById(winnerIds[i]);
            }

            string winnerString = winnerStringStart + winnerStringEnd;
            Overlay.ShowNotification("Game is finished!");
            Overlay.ShowNotification(winnerString);

            //stop game
            GameHasEnded = true;
            Overlay.ShowEndScreen();
        }

        /// <summary>
        /// Leaves the game, restarting the application
        /// </summary>
        public void LeaveMatch()
        {
            Console.WriteLine("Server shutting down. Close match.");
            Application.Restart();
        }

        /// <summary>
        /// Retrieves a player's name by id.
        /// </summary>
        /// <param name="playerId">Id of the player.</param>
        /// <returns>Returns name of the player.</returns>
        private string GetPlayernameById(int playerId)
        {
            if (Player.Instance.Id.Equals(playerId))
            {
                return "You";
            }
            else
            {
                lock (this.Opponents)
                {
                    int opponentCount = this.Opponents.Count;
                    for (int i = opponentCount - 1; i >= 0; i--)
                    {
                        if (this.Opponents[i].Id.Equals(playerId))
                        {
                            return Opponents[i].Name;
                        }
                    }
                }
            }
            return "[user not found]";
        }

        /// <summary>
        /// Updates the GameManager data.
        /// </summary>
        public void Update(GameTime gameTime)
        {
            if (!GameManager.Instance.GameHasEnded)
            {
                foreach (Opponent opponent in this.Opponents)
                {
                    opponent.Update(gameTime);
                }

                lock (this.Projectiles)
                {
                    // Reversed iteration to prevent exceptions on collection change
                    int projectileCount = this.Projectiles.Count;
                    for (int i = projectileCount - 1; i >= 0; i--)
                    {
                        this.Projectiles[i].Update(gameTime);
                        if (this.Projectiles[i].HasExceededDeletingBounds())
                        {
                            this.Projectiles.Remove(this.Projectiles[i]);
                        }
                    }
                }
            }

            // Explosions continue even if the game has ended
            lock (this.Explosions)
            {
                int explosionCount = this.Explosions.Count;
                for (int i = explosionCount - 1; i >= 0; i--)
                {
                    this.Explosions[i].Update(gameTime);

                    // Remove the explosion if finished
                    if (this.Explosions[i].HasFinished)
                    {
                        this.Explosions.RemoveAt(i);
                    }
                }
            }
        }

        /// <summary>
        /// Draws the GameManager data.
        /// </summary>
        public void Draw(SpriteBatch spriteBatch)
        {
            int opponentCount = this.Opponents.Count;
            foreach (Opponent opponent in this.Opponents)
            {
                opponent.Draw(spriteBatch);
            }

            lock (this.Projectiles)
            {
                // Reversed iteration to prevent exceptions on collection change
                int projectileCount = this.Projectiles.Count;
                for (int i = projectileCount - 1; i >= 0; i--)
                {
                    this.Projectiles[i].Draw(spriteBatch);
                }
            }

            int explosionCount = this.Explosions.Count;
            for (int i = explosionCount - 1; i >= 0; i--)
            {
                this.Explosions[i].Draw(spriteBatch);
            }

            Player.Instance.Crosshair.Draw(spriteBatch);
        }
    }
}
